package com.atom.module.lib.opengles.shader

import android.opengl.GLES30
import com.atom.module.lib.opengles.draw.GlBindable
import com.atom.module.lib.opengles.draw.GlDrawable
import com.atom.module.lib.opengles.draw.use
import com.atom.module.lib.opengles.util.ShaderUtils
import com.blankj.utilcode.util.LogUtils

open class GlProgram(
    protected val handle: Int,
    protected val ownsHandle: Boolean,// 是否需要去编译组装
    protected vararg val shaders: GlShader
) : GlBindable {

    constructor(handle: Int) : this(handle, false)

    constructor(vertexShader: String, fragmentShader: String) : this(
        GlShader(GLES30.GL_VERTEX_SHADER, vertexShader),
        GlShader(GLES30.GL_FRAGMENT_SHADER, fragmentShader)
    ) {
        LogUtils.e(vertexShader, fragmentShader)
    }

    constructor(vararg shaders: GlShader) : this(create(*shaders), true, *shaders)

    private var isReleased = false

    open fun release() {
        if (!isReleased) {
            if (ownsHandle) ShaderUtils.deleteProgram(handle)
            shaders.forEach { it.release() }
            isReleased = true
        }
    }

    override fun bind() {
        GLES30.glUseProgram(handle)
        ShaderUtils.checkGLError("bind glUseProgram")
    }

    override fun unbind() {
        GLES30.glUseProgram(0)
    }

    @JvmOverloads
    fun draw(
        drawable: GlDrawable,
        modelViewProjectionMatrix: FloatArray = drawable.modelMatrix
    ) {
        ShaderUtils.checkGLError("draw start")
        use {
            onPreDraw(drawable, modelViewProjectionMatrix)
            onDraw(drawable)
            onPostDraw(drawable)
        }
        ShaderUtils.checkGLError("draw end")
    }


    open fun onPreDraw(drawable: GlDrawable, modelViewProjectionMatrix: FloatArray) {}

    open fun onDraw(drawable: GlDrawable) {
        drawable.draw()
    }

    open fun onPostDraw(drawable: GlDrawable) {}

    protected fun getAttribHandle(name: String): GlProgramLocation =
        GlProgramLocation.getAttrib(handle, name)

    protected fun getUniformHandle(name: String): GlProgramLocation =
        GlProgramLocation.getUniform(handle, name)


    companion object {

        @JvmStatic
        fun create(vararg shaders: GlShader): Int {
            val program = GLES30.glCreateProgram()
            ShaderUtils.checkGLError("glCreateProgram")
            if (program == 0) {
                throw RuntimeException("Could not create program")
            }
            shaders.forEach { shader ->
                GLES30.glAttachShader(program, shader.id)
                ShaderUtils.checkGLError("glAttachShader ${shader.type}")
            }
            GLES30.glLinkProgram(program)
            val linkStatus = IntArray(1)
            GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linkStatus, 0)
            if (linkStatus[0] != GLES30.GL_TRUE) {
                val message = "Could not link program: " + GLES30.glGetProgramInfoLog(program)
                GLES30.glDeleteProgram(program)
                throw RuntimeException(message)
            }
            return program
        }


    }
}